using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Ajax;
using System.Web.Mvc.Html;
using System.Web.Routing;
using System.Xml.Xsl;

namespace Xmlt.ViewEngine
{

    public class XmltView : IViewDataContainer, IView
    {
        private readonly string ajaxDeclaration;
        private readonly object viewData;
        private readonly XmltTemplate viewTemplate;
        private readonly XslCompiledTransform xslTransformer;
        private ViewContext viewContext;

        public XmltView(XmltTemplate viewTemplate, object viewData, string ajaxDeclaration, HttpContextBase httpContext)
        {
            this.viewTemplate = viewTemplate;
            this.viewData = viewData;
            this.ajaxDeclaration = ajaxDeclaration;

            xslTransformer = viewTemplate.XslTransformer;
        }

        public ViewDataDictionary ViewData
        {
            get { return viewContext.ViewData; }
            set { throw new NotSupportedException(); }
        }


        public void Render(ViewContext viewContext, TextWriter writer)
        {
            if (viewData is int && (int)viewData == 0)
            {
                writer.Write(viewTemplate.TransformText);
                return;
            } 
            this.viewContext = viewContext;
            //TODO: viewContext.HttpContext.Response.ContentType = "text/html";
            string url = viewContext.HttpContext.Request.Url.ToString();
            //construct.AppendPage("", url, viewData.PageVars);

            var args = new XsltArgumentList();
            HtmlHelper html = new HtmlHelper(viewContext, this);
            AjaxHelper ajax = new AjaxHelper(viewContext, this);
            args.AddExtensionObject("urn:HtmlHelper", html);
            args.AddExtensionObject("urn:LinkExtensions", new LinkExtensionsWrapper(html));
            args.AddExtensionObject("urn:ChildActionExtensions", new ChildActionExtensionsWrapper(html));
            args.AddExtensionObject("urn:RenderPartialExtensions", new RenderPartialExtensionsWrapper(html));
            args.AddExtensionObject("urn:AjaxExtensions", new AjaxExtensionsWrapper(ajax));
            args.AddExtensionObject("urn:UrlHelperExtensions", new UrlHelperExtensionsWrapper(html));
            args.AddExtensionObject("urn:ValidationExtensions", new ValidationExtensionsWrapper(html));
            args.AddExtensionObject("urn:FormExtensions", new FormExtensionsWrapper(html));
            args.AddParam("AjaxProScriptReferences", "", ajaxDeclaration);
            args.AddParam("html", "", html);

            foreach (var pair in ViewData) {
                if (pair.Key.Contains(":") && !pair.Value.GetType().IsPrimitive)
                {
                    args.AddExtensionObject(pair.Key, pair.Value);
                }
                else
                {
                    args.AddParam(pair.Key, "", pair.Value);
                }
            }

            System.IO.MemoryStream ms = new System.IO.MemoryStream();
            System.IO.StreamWriter s = new System.IO.StreamWriter(ms);
            System.Xml.Serialization.XmlSerializer x = new System.Xml.Serialization.XmlSerializer(viewData.GetType());
            x.Serialize(s, viewData);
            s.Flush();
            ms.SetLength(ms.Position);
            ms.Position = 0;

            System.Diagnostics.Stopwatch time = new System.Diagnostics.Stopwatch();
            time.Start();
            System.Xml.XmlWriter xmlWriter = System.Xml.XmlWriter.Create(writer, xslTransformer.OutputSettings);
            xslTransformer.Transform(System.Xml.XmlReader.Create(ms), args, xmlWriter, new XmlVirtualPathResolver(viewContext.HttpContext.Request.Url.ToString()));
            time.Stop();
            xmlWriter.WriteComment(string.Format("{0}", time.ElapsedTicks));
            xmlWriter.Flush();
            //xslTransformer.Transform(System.Xml.XmlReader.Create(ms), args, writer);
        }

    }

    internal class FormExtensionsWrapper
    {
        
        HtmlHelper m_htmlHelper;

        public FormExtensionsWrapper(HtmlHelper htmlHelper)
        {
            m_htmlHelper = htmlHelper;
        }

        private Stack<FormContext> stack = new Stack<FormContext>(1);

        public int BeginForm(System.Xml.XPath.XPathNavigator context, string id)
        {
            stack.Push(m_htmlHelper.ViewContext.FormContext);
            m_htmlHelper.ViewContext.FormContext = new FormContext();
            m_htmlHelper.ViewContext.FormContext.FormId = id;
            return 0;
        }

        public string EndForm(int id)
        {
            TextWriter currentWriter = m_htmlHelper.ViewContext.Writer;
            TextWriter newWriter = new System.IO.StringWriter();
            m_htmlHelper.ViewContext.Writer = newWriter;
            m_htmlHelper.ViewContext.OutputClientValidation();
            m_htmlHelper.ViewContext.Writer = currentWriter;
            m_htmlHelper.ViewContext.FormContext = stack.Pop();
            return newWriter.ToString();
        }
                
    }

    internal class UrlHelperExtensionsWrapper {
        
        HtmlHelper m_htmlHelper;

        public UrlHelperExtensionsWrapper(HtmlHelper htmlHelper) {
            m_htmlHelper = htmlHelper;
        }

        public string GenerateUrl(string routeName, string actionName, string controllerName, object unknown1)
        {
            RouteValueDictionary routeValues;
            if (XsltMVCHelper.ParseRouteValues(unknown1, out routeValues)) {
                return UrlHelper.GenerateUrl(routeName, actionName, controllerName, routeValues, m_htmlHelper.RouteCollection, m_htmlHelper.ViewContext.RequestContext, true);                
            } else {
                throw new ArgumentException("unknown1");
            }
        }

    }

    internal class ValidationExtensionsWrapper
    {
        HtmlHelper m_htmlHelper;


        public ValidationExtensionsWrapper(HtmlHelper htmlHelper)
        {
            m_htmlHelper = htmlHelper;
        }

        public string ValidationMessage(string modelName)
        {
            MvcHtmlString htmlstring = System.Web.Mvc.Html.ValidationExtensions.ValidationMessage(m_htmlHelper, modelName);
            if (htmlstring != null)
                return htmlstring.ToHtmlString();
            return "";
        }

        public string ValidationMessage(string modelName, object unknown1)
        {
            RouteValueDictionary parsedRouteValues;
            if (XsltMVCHelper.ParseRouteValues(unknown1, out parsedRouteValues))
                // overload 1
                return System.Web.Mvc.Html.ValidationExtensions.ValidationMessage(m_htmlHelper, modelName, parsedRouteValues).ToHtmlString();
            else
                throw new ArgumentException("unknown1");

        }

        public string ValidationSummary()
        {

            MvcHtmlString htmlstring = System.Web.Mvc.Html.ValidationExtensions.ValidationSummary(m_htmlHelper, false);
            if (htmlstring != null)
                return htmlstring.ToHtmlString();
            return "";

        }

    }

    public static class AjaxOptionsExtensions
    {
        internal static string InsertionModeToString(InsertionMode insertionMode)
        {
            switch (insertionMode)
            {
                case InsertionMode.Replace:
                    return "Sys.Mvc.InsertionMode.replace";
                case InsertionMode.InsertBefore:
                    return "Sys.Mvc.InsertionMode.insertBefore";
                case InsertionMode.InsertAfter:
                    return "Sys.Mvc.InsertionMode.insertAfter";
                default:
                    return ((int)insertionMode).ToString(CultureInfo.InvariantCulture);
            }
        }

        internal static string ToJavascriptString(this AjaxOptions ajaxOptions)
        {
            StringBuilder optionsBuilder = new StringBuilder("{");
            optionsBuilder.Append(String.Format(CultureInfo.InvariantCulture, " insertionMode: {0},", InsertionModeToString(ajaxOptions.InsertionMode)));
            optionsBuilder.Append(PropertyStringIfSpecified("confirm", ajaxOptions.Confirm));
            optionsBuilder.Append(PropertyStringIfSpecified("httpMethod", ajaxOptions.HttpMethod));
            optionsBuilder.Append(PropertyStringIfSpecified("loadingElementId", ajaxOptions.LoadingElementId));
            optionsBuilder.Append(PropertyStringIfSpecified("updateTargetId", ajaxOptions.UpdateTargetId));
            optionsBuilder.Append(PropertyStringIfSpecified("url", ajaxOptions.Url));
            optionsBuilder.Append(EventStringIfSpecified("onBegin", ajaxOptions.OnBegin));
            optionsBuilder.Append(EventStringIfSpecified("onComplete", ajaxOptions.OnComplete));
            optionsBuilder.Append(EventStringIfSpecified("onFailure", ajaxOptions.OnFailure));
            optionsBuilder.Append(EventStringIfSpecified("onSuccess", ajaxOptions.OnSuccess));
            optionsBuilder.Length--;
            optionsBuilder.Append(" }");
            return optionsBuilder.ToString();
        }
        
        private static string PropertyStringIfSpecified(string propertyName, string propertyValue)
        {
            if (!String.IsNullOrEmpty(propertyValue))
            {
                string escapedPropertyValue = propertyValue.Replace("'", @"\'");
                return String.Format(CultureInfo.InvariantCulture, " {0}: '{1}',", propertyName, escapedPropertyValue);
            }
            return String.Empty;
        }

        private static string EventStringIfSpecified(string propertyName, string handler)
        {
            if (!String.IsNullOrEmpty(handler))
            {
                return String.Format(CultureInfo.InvariantCulture, " {0}: Function.createDelegate(this, {1}),", propertyName, handler.ToString());
            }
            return String.Empty;
        }
    }

    internal class AjaxExtensionsWrapper
    {
        private const string LinkOnClickFormat = "Sys.Mvc.AsyncHyperlink.handleClick(this, new Sys.UI.DomEvent(event), {0});";
        private const string FormOnClickValue = "Sys.Mvc.AsyncForm.handleClick(this, new Sys.UI.DomEvent(event));";
        private const string FormOnSubmitFormat = "Sys.Mvc.AsyncForm.handleSubmit(this, new Sys.UI.DomEvent(event), {0});";
        private const string _globalizationScript = @"<script type=""text/javascript"" src=""{0}""></script>";

        AjaxHelper m_ajaxHelper;

        public AjaxExtensionsWrapper(AjaxHelper ajaxHelper)
        {
            m_ajaxHelper = ajaxHelper;
        }

        public string AjaxLink(object unknown1)
        {
            System.Web.Mvc.Ajax.AjaxOptions ajaxOptions = new AjaxOptions();
            if (XsltMVCHelper.ParseAjaxOptions(unknown1, out ajaxOptions))
            {
                // overload 1
                string optionsString = ajaxOptions.ToJavascriptString();
                return String.Format(CultureInfo.InvariantCulture, LinkOnClickFormat, optionsString);
            } 
            else
                throw new ArgumentException("unknown1");

        }

        public string AjaxSubmit(object unknown1)
        {
            System.Web.Mvc.Ajax.AjaxOptions ajaxOptions = new AjaxOptions();
            if (XsltMVCHelper.ParseAjaxOptions(unknown1, out ajaxOptions))
            {
                // overload 1
                string optionsString = ajaxOptions.ToJavascriptString();
                return String.Format(CultureInfo.InvariantCulture, FormOnSubmitFormat, optionsString);
            }
            else
                throw new ArgumentException("unknown1");

        }

    }

    internal class RenderPartialExtensionsWrapper
    {
        private HtmlHelper m_htmlHelper;

        public RenderPartialExtensionsWrapper(HtmlHelper htmlHelper)
        {
            m_htmlHelper = htmlHelper;
        }

        public string RenderPartial(string partialViewName) {
            TextWriter oldWriter = m_htmlHelper.ViewContext.Writer;
            StringBuilder sb = new StringBuilder();
            m_htmlHelper.ViewContext.Writer = new StringWriter(sb);
            RenderPartialExtensions.RenderPartial(m_htmlHelper, partialViewName);
            m_htmlHelper.ViewContext.Writer = oldWriter;
            return sb.ToString();
        }
    }
    
    internal class ChildActionExtensionsWrapper
    {
        private HtmlHelper m_htmlHelper;

        public ChildActionExtensionsWrapper(HtmlHelper htmlHelper)
        {
            m_htmlHelper = htmlHelper;
        }

        public void RenderAction(string actionName)
        {
            System.Web.Mvc.Html.ChildActionExtensions.RenderAction(m_htmlHelper, actionName);
        }

        public void RenderAction(string actionName, string controllerName)
        {
            System.Web.Mvc.Html.ChildActionExtensions.RenderAction(m_htmlHelper, actionName, controllerName);
        }

        public void RenderAction(string actionName, string controllerName, object routeValues)
        {
            RouteValueDictionary parsedRouteValues;
            if (XsltMVCHelper.ParseRouteValues(routeValues, out parsedRouteValues))
                System.Web.Mvc.Html.ChildActionExtensions.RenderAction(m_htmlHelper, actionName, controllerName, parsedRouteValues);
            else
                System.Web.Mvc.Html.ChildActionExtensions.RenderAction(m_htmlHelper, actionName, controllerName, routeValues);
        }

        public string Action(string actionName)
        {
            return System.Web.Mvc.Html.ChildActionExtensions.Action(m_htmlHelper, actionName).ToString();
        }

        public string Action(string actionName, string controllerName)
        {
            return System.Web.Mvc.Html.ChildActionExtensions.Action(m_htmlHelper, actionName, controllerName).ToString();
        }


        public string Action(string actionName, string controllerName, object routeValues)
        {
            RouteValueDictionary parsedRouteValues;
            if (XsltMVCHelper.ParseRouteValues(routeValues, out parsedRouteValues))
                return System.Web.Mvc.Html.ChildActionExtensions.Action(m_htmlHelper, actionName, controllerName, parsedRouteValues).ToString();
            else
                return System.Web.Mvc.Html.ChildActionExtensions.Action(m_htmlHelper, actionName, controllerName, routeValues).ToString();
        }

    }

    [TypeDescriptionProvider(typeof(RouteValuesStringTypeDescriptionProvider))]
    internal class RouteValuesString
    {

        public string Value { get; private set; }
        public RouteValuesString(string value)
        {

        }

        private Dictionary<String, Object> customFieldValues =
                               new Dictionary<String, Object>();

        public Object this[String fieldName]
        {
            get
            {
                Object value = null;
                customFieldValues.TryGetValue(fieldName, out value);
                return value;
            }

            set
            {
                customFieldValues[fieldName] = value;
            }
        }

    }

    internal class RouteValuesStringTypeDescriptionProvider : TypeDescriptionProvider
    {
        private static TypeDescriptionProvider defaultTypeProvider =
                   TypeDescriptor.GetProvider(typeof(RouteValuesString));

        public RouteValuesStringTypeDescriptionProvider()
            : base(defaultTypeProvider)
        {
        }

        public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType,
                                                                object instance)
        {
            ICustomTypeDescriptor defaultDescriptor =
                                  base.GetTypeDescriptor(objectType, instance);

            return instance == null ? defaultDescriptor :
                new RouteValuesCustomTypeDescriptor(defaultDescriptor, instance);
        }
    }

    class CustomFieldPropertyDescriptor : PropertyDescriptor
    {
        public new string Name { get; private set; }
        public string Value { get; private set; }

        public CustomFieldPropertyDescriptor(string name, string value)
            : base(name, new Attribute[0])
        {
            Name = name;
            Value = value;
        }

        public override bool CanResetValue(object component)
        {
            return false;
        }

        public override Type ComponentType
        {
            get
            {
                return typeof(RouteValuesString);
            }
        }

        public override object GetValue(object component)
        {
            RouteValuesString title = (RouteValuesString)component;
            return title[Name];/* ??  (CustomField.DataType.IsValueType ?
                (Object)Activator.CreateInstance(CustomField.DataType) : null);
                                */
        }

        public override bool IsReadOnly
        {
            get
            {
                return false;
            }
        }

        public override Type PropertyType
        {
            get
            {
                return typeof(string);// CustomField.DataType;
            }
        }

        public override void ResetValue(object component)
        {
            throw new NotImplementedException();
        }

        public override void SetValue(object component, object value)
        {
            RouteValuesString title = (RouteValuesString)component;
            title[Name] = value;
        }

        public override bool ShouldSerializeValue(object component)
        {
            return false;
        }
    }

    internal class RouteValuesCustomTypeDescriptor : CustomTypeDescriptor
    {

        public RouteValuesCustomTypeDescriptor(ICustomTypeDescriptor parent, object instance)
            : base(parent)
        {
            RouteValuesString id = (RouteValuesString)instance;
            customFields.Add(new CustomFieldPropertyDescriptor("id", id.Value));

        }

        private List<PropertyDescriptor> customFields = new List<PropertyDescriptor>();

        public override PropertyDescriptorCollection GetProperties()
        {
            return new PropertyDescriptorCollection(base.GetProperties()
                .Cast<PropertyDescriptor>().Union(customFields).ToArray());
        }

        public override PropertyDescriptorCollection GetProperties(Attribute[] attributes)
        {
            return new PropertyDescriptorCollection(base.GetProperties(attributes)
                .Cast<PropertyDescriptor>().Union(customFields).ToArray());
        }
    }

    internal class LinkExtensionsWrapper
    {

        private HtmlHelper m_htmlHelper;

        public LinkExtensionsWrapper(HtmlHelper htmlHelper)
        {
            m_htmlHelper = htmlHelper;
        }

        public string ActionLink(string actionName)
        {
            return UrlHelper.GenerateUrl(null, actionName, null, null, null, null, null, m_htmlHelper.RouteCollection, m_htmlHelper.ViewContext.RequestContext, true);
        }

        public string ActionLink(string actionName, object unknown1)
        {
            RouteValueDictionary parsedRouteValues;
            if (XsltMVCHelper.ParseRouteValues(unknown1, out parsedRouteValues))
                // overload 2/3
                return UrlHelper.GenerateUrl(null, actionName, null, null, null, null, parsedRouteValues, m_htmlHelper.RouteCollection, m_htmlHelper.ViewContext.RequestContext, true);
            else if (unknown1 is string)
                // overload 4
                return UrlHelper.GenerateUrl(null, actionName, unknown1 as string, null, null, null, null, m_htmlHelper.RouteCollection, m_htmlHelper.ViewContext.RequestContext, true);
            else
                throw new ArgumentException("unknown1");
        }

        public string ActionLink(string actionName, string controllerName, object unknown1)
        {
            RouteValueDictionary parsedRouteValues;
            
            if (XsltMVCHelper.ParseRouteValues(unknown1, out parsedRouteValues))
                // 
                return UrlHelper.GenerateUrl(null, actionName, controllerName as string, null, null, null, parsedRouteValues, m_htmlHelper.RouteCollection, m_htmlHelper.ViewContext.RequestContext, true);
            else
                throw new ArgumentException("unknown1");
        
        }

    }
}
